Spring Boot 的自动装配源于Spring Framework的手动装配。
1. Spring模式注解装配
定义:一种用于声明在应用中扮演”组件”角色的注解
举例
Spring框架中常见的@Component
、@Service
、@Configuration
等等。
装配方式:
配置文件xml中配置<context:component-scan>
1 2 3 4 5 6 7 8 9 10 11 12
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/springcontext.xsd">
<context:annotation-config />
<context:component-scan base-package="com.test.spring.boot" /> </beans>
|
@ComponentScan
1 2 3
| @ComponentScan(basePackages = "com.test.spring.boot") public class SpringConfiguration { ... }
|
自定义模式注解
前往SPRING INITIALIZR 生成一个工程。
点击Generate Project等待下载完成后解压,使用IDEA导入工程,选MAVEN工程一直下一步即可。
导入后等待MAVEN下载好依赖,完成后截图如下。
下面开始自定义一个注解,新建一个包annotation
,在这个包里新建一个注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package cn.myjdemo.springbootlearning.annotation;
import org.springframework.stereotype.Repository; import java.lang.annotation.*;
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repository public @interface FirstLevelRepository { String value() default ""; }
|
按住ctrl点击@Repository
可以看到Repository
注解的代码

继续点进Repository
中的@Component

根据Spring官方的一句话
凡是被@Component
元标注的注解,如@Service
,当任何组件标注它时,也被视作组件扫描的候选对象。
在这边@Repository
标注了@Component
注解,也就是说当一个组件标注了@Repository
注解时,也会被视作@Component
扫描到,这就是模式注解的派生性。这里我们注意,这些注解中的String value() default "";
也保持一致。
我们自定义的@FirstLevelRepository
上面标注了@Repository
,@Repository
上标注了@Component
,因此,当一个组件标注了我们自定义的@FirstLevelRepository
注解时,也会被扫描到。
下面我们来编写代码验证一下。先建一个repository
包,里面存放我们测试的Bean,在这个包里建一个测试Bean并且使用我们自定义的注解。
1 2 3 4 5 6
| package cn.myjdemo.springbootlearning.repository;
import cn.myjdemo.springbootlearning.annotation.FirstLevelRepository;
@FirstLevelRepository(value = "myFirstLevelRepository") public class MyFirstLevelRepository {}
|
在建一个bootstrap
包,里面建一个RepositoryBootstrap
引导类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package cn.myjdemo.springbootlearning.bootstrap;
import cn.myjdemo.springbootlearning.repository.MyFirstLevelRepository; import org.springframework.boot.WebApplicationType; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan;
@ComponentScan(basePackages = "cn.myjdemo.springbootlearning.repository") public class RepositoryBootstrap { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(RepositoryBootstrap.class) .web(WebApplicationType.NONE) .run(args); MyFirstLevelRepository repository = context.getBean("myFirstLevelRepository",MyFirstLevelRepository.class); System.out.println(repository); context.close(); } }
|
运行main
方法,发现已经扫描到我们注解的类并且打印出来了。
而另一个特性层次性就是说我们自定义的这个注解不仅能标注到组件上面去,也可以标注到另一个注解上面。
我们在annotation
中再新建一个注解@SecondLevelRepository
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package cn.myjdemo.springbootlearning.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @FirstLevelRepository public @interface SecondLevelRepository { String value() default ""; }
|
然后将repository
的注解改为SecondLevelRepository
1 2 3 4 5 6
| package cn.myjdemo.springbootlearning.repository;
import cn.myjdemo.springbootlearning.annotation.SecondLevelRepository;
@SecondLevelRepository(value = "myFirstLevelRepository") public class MyFirstLevelRepository {}
|
然后直接运行之前的main
方法,仍然能扫描到。
2.Spring @Enable模块装配
定义:具备相同领域的功能组件集合,组合所形成的一个独立的单元。
举例
@EnableWebMvc
、@EnableAutoConfiguration
等
实现方式
- 注解驱动方式
1 2 3 4 5 6
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
|
1 2 3 4
| @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport{ ... }
|
- 接口编程方式
1 2 3 4 5 6
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(CachingConfigurationSelector.class) public @interface EnableCaching { }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[]{ AutoProxyRegistrar.class.getName(),ProxyCachingConfiguration.class.getName() }; case ASPECTJ: return new String[] { AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME }; default: return null; } }
|
自定义@Enable模块
- 基于注解
@EnableHelloWorld
在annotaion
包中新建一个注解EnableHelloWorld
1 2 3 4 5 6 7 8 9 10 11 12 13
| package cn.myjdemo.springbootlearning.annotation;
import cn.myjdemo.springbootlearning.configuration.HelloWorldConfiguration; import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(HelloWorldConfiguration.class) public @interface EnableHelloWorld { }
|
然后新建一个configuration
包,然后在里面新建一个HelloWorldConfiguration
类
1 2 3 4 5 6 7 8 9 10 11 12 13
| package cn.myjdemo.springbootlearning.configuration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class HelloWorldConfiguration {
@Bean public String helloWorld(){ return "Hello World"; } }
|
最后在bootstrap
包中新建一个EnableHelloWorldBootstrap
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package cn.myjdemo.springbootlearning.bootstrap;
import cn.myjdemo.springbootlearning.annotation.EnableHelloWorld; import org.springframework.boot.WebApplicationType; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext;
@EnableHelloWorld public class EnableHelloWorldBootstrap {
public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class) .web(WebApplicationType.NONE) .run(args); String helloWorld = context.getBean("helloWorld",String.class);
System.out.println(helloWorld);
context.close(); } }
|
运行main
函数,能发现控制台正常输出的Hello World
- 基于接口编程方式
先在annotation
包中新建一个类
1 2 3 4 5 6 7 8 9 10 11 12
| package cn.myjdemo.springbootlearning.annotation;
import cn.myjdemo.springbootlearning.configuration.HelloWorldConfiguration; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata;
public class HelloWorldImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{HelloWorldConfiguration.class.getName()}; } }
|
然后将之前的EnableHelloWorld
注解中@Import(HelloWorldConfiguration.class)
注释掉,修改为@Import(HelloWorldImportSelector.class)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package cn.myjdemo.springbootlearning.annotation;
import cn.myjdemo.springbootlearning.configuration.HelloWorldConfiguration; import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented
@Import(HelloWorldImportSelector.class) public @interface EnableHelloWorld { }
|
运行EnableHelloWorldBootstrap
,也能正常打印出Hello World
基于接口编程的方式我们可以在Selector
中制定条件语句,能有多种返回值(见举例中的CachingConfigurationSelector
),是弹性的,而基于注解的方式就不可以。
3.Spring条件装配
定义:Bean装配的前置判断
举例
@Profile
、@Conditional
等
实现方式
- 配置方式
@Profile
- 编程方式
@Conditional
1 2 3 4 5 6 7 8 9
| @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional({OnClassCondition.class}) public @interface ConditionalOnClass { Class<?>[] value() default {};
String[] name() default {}; }
|
自定义条件装配
- 基于配置方式实现
@Profile
下面模拟一个计算服务,多整数求和sum,@Profile("java7")
是假设在java7版本下的求和运算,使用for循环实现;@Profile("java8")
是假设在java8版本下的求和运算,使用lambda表达式实现。
先新建一个service
包,然后在里面写一个计算接口CalculateService
1 2 3 4 5 6 7 8 9 10
| package cn.myjdemo.springbootlearning.service;
public interface CalculateService {
Integer sum(Integer... values); }
|
然后在这个包下面分别写两个实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package cn.myjdemo.springbootlearning.service;
import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @Profile("java7") @Service public class Java7CalculateService implements CalculateService { @Override public Integer sum(Integer... values) { System.out.println("java7 for循环实现"); int sum = 0; for(int i=0;i<values.length;i++){ sum += values[i]; } return sum; }
public static void main(String[] args) { CalculateService calculateService = new Java7CalculateService(); System.out.println(calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package cn.myjdemo.springbootlearning.service;
import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service;
import java.util.stream.Stream;
@Profile("java8") @Service public class Java8CalculateService implements CalculateService { @Override public Integer sum(Integer... values) { System.out.println("java8 lambda实现"); return Stream.of(values).reduce(0,Integer::sum); }
public static void main(String[] args) { CalculateService calculateService = new Java8CalculateService(); System.out.println(calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); }
}
|
然后在bootstrap
包中新建一个CalculateServiceBootstrap
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package cn.myjdemo.springbootlearning.bootstrap;
import cn.myjdemo.springbootlearning.service.CalculateService; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication(scanBasePackages = "cn.myjdemo.springbootlearning.service") public class CalculateServiceBootstrap {
public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootstrap.class) .web(WebApplicationType.NONE) .profiles("java7") .run(args); CalculateService calculateService = context.getBean(CalculateService.class);
System.out.println(calculateService.sum(1,2,3,4,5,6,7,8,9,10));
context.close(); } }
|
由于配置了java7,因此会使用java7的实现类,将.profiles("java7")
改成.profiles("java8")
之后,就会改用java8的实现类。
- 基于编程方式实现
@ConditionalOnSystemProperty
先新建一个包condition
,新建一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package cn.myjdemo.springbootlearning.condition;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata;
import java.util.Map;
public class OnSystemPropertyConditional implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Map<String,Object> attributes = annotatedTypeMetadata.getAnnotationAttributes(ConditionalOnSystemProperty.class.getName());
String propertyName = String.valueOf(attributes.get("name")); String propertyValue = String.valueOf(attributes.get("value"));
String javaPropertyValue = System.getProperty(propertyName);
return propertyValue.equals(javaPropertyValue); } }
|
再新建一个注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package cn.myjdemo.springbootlearning.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Documented @Conditional({OnSystemPropertyConditional.class}) public @interface ConditionalOnSystemProperty {
String name();
String value(); }
|
再在bootstrap包中新建一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package cn.myjdemo.springbootlearning.bootstrap;
import cn.myjdemo.springbootlearning.condition.ConditionalOnSystemProperty; import org.springframework.boot.WebApplicationType; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean;
public class ConditionalOnSystemPropertyBootstrap {
@Bean @ConditionalOnSystemProperty(name = "user.name",value = "Administrator") public String helloWorld(){ return "Hello World"; }
public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(ConditionalOnSystemPropertyBootstrap.class) .web(WebApplicationType.NONE) .run(args); String helloWorld = context.getBean("helloWorld",String.class);
System.out.println(helloWorld);
context.close(); }
}
|
运行后能正常输出Hello World。当把ConditionalOnSystemPropertyBootstrap中helloWorld()方法上的注解的value改为非系统属性中user.name的值时,就会报扫描不到bean的错,因为我们在OnSystemPropertyConditional中编写了只有当从系统取出来的name对应的value值与我们注解里name对应的value值一致时才能被扫描到。